# frozen_string_literal: true

class ProjectFolder < ApplicationRecord
  ID_PREFIX = 'PF'
  include PrefixedIdModel
  SEARCHABLE_ATTRIBUTES = ['project_folders.name', PREFIXED_ID_SQL].freeze

  include ArchivableModel
  include SearchableModel
  include SearchableByNameModel

  validates :name,
            length: { minimum: Constants::NAME_MIN_LENGTH,
                      maximum: Constants::NAME_MAX_LENGTH },
            uniqueness: { scope: %i(team_id parent_folder_id archived), case_sensitive: false }
  validate :parent_folder_team, if: -> { parent_folder.present? }
  validate :parent_folder_validation, if: -> { parent_folder.present? }

  before_validation :inherit_team_from_parent_folder, on: :create, if: -> { parent_folder.present? }
  before_validation :ensure_uniqueness_name_on_moving, on: :update, if: -> { parent_folder_id_changed? }

  belongs_to :team, inverse_of: :project_folders, touch: true
  belongs_to :parent_folder, class_name: 'ProjectFolder', optional: true, touch: true
  belongs_to :archived_by, foreign_key: 'archived_by_id',
                           class_name: 'User',
                           inverse_of: :archived_project_folders,
                           optional: true
  belongs_to :restored_by, foreign_key: 'restored_by_id',
                           class_name: 'User',
                           inverse_of: :restored_project_folders,
                           optional: true
  has_many :projects, inverse_of: :project_folder, dependent: :nullify
  has_many :project_folders, foreign_key: 'parent_folder_id', inverse_of: :parent_folder, dependent: :destroy

  scope :top_level, -> { where(parent_folder: nil) }

  def self.viewable_by_user(user, teams)
    joins(team: :users)
      .where(teams: { user_assignments: { user: user } })
      .where(team: teams)
  end

  def self.search(user, include_archived, query = nil, current_team = nil, options = {})
    teams = options[:teams] || current_team || user.teams.select(:id)

    new_query = distinct.viewable_by_user(user, teams)
                        .where_attributes_like_boolean(SEARCHABLE_ATTRIBUTES, query, options)
    new_query = new_query.active unless include_archived

    new_query
  end

  def self.inner_folders(team, project_folder = nil)
    entry_point_condition = project_folder ? 'parent_folder_id = ?' : 'parent_folder_id IS NULL'
    inner_folders_sql =
      "WITH RECURSIVE inner_project_folders(id, selected_folders_ids) AS (
        SELECT id, ARRAY[id]
        FROM project_folders
        WHERE team_id = ? AND #{entry_point_condition}
        UNION ALL
        SELECT project_folders.id, selected_folders_ids || project_folders.id
        FROM inner_project_folders
        JOIN project_folders ON project_folders.parent_folder_id = inner_project_folders.id
        WHERE NOT project_folders.id = ANY(selected_folders_ids)
      )
      SELECT id FROM inner_project_folders ORDER BY selected_folders_ids".gsub(/\n|\t/, ' ').gsub(/ +/, ' ')

    if project_folder.present?
      where("project_folders.id IN (#{inner_folders_sql})", team.id, project_folder.id)
    else
      where("project_folders.id IN (#{inner_folders_sql})", team.id)
    end
  end

  def self.filter_by_teams(teams = [])
    teams.blank? ? self : where(team: teams)
  end

  def parent_folders
    outer_folders_sql =
      'WITH RECURSIVE outer_project_folders(id, parent_folder_id, selected_folders_ids) AS (
        SELECT id, parent_folder_id, ARRAY[id]
        FROM project_folders
        WHERE team_id = ? AND id = ?
        UNION ALL
        SELECT project_folders.id, project_folders.parent_folder_id, selected_folders_ids || project_folders.id
        FROM outer_project_folders
        JOIN project_folders ON project_folders.id = outer_project_folders.parent_folder_id
        WHERE NOT project_folders.id = ANY(selected_folders_ids)
      )
      SELECT id FROM outer_project_folders ORDER BY selected_folders_ids'.gsub(/\n|\t/, ' ').gsub(/ +/, ' ')

    ProjectFolder.where("project_folders.id IN (#{outer_folders_sql})", team.id, id)
  end

  def inner_projects
    Project.where(project_folder: ProjectFolder.inner_folders(team, self) + [self])
  end

  def archived_branch?
    return false if active? && parent_folder.blank?
    return true if archived?

    parent_folder.present? && parent_folder.archived_branch?
  end

  private

  def inherit_team_from_parent_folder
    self.team = parent_folder.team
  end

  def parent_folder_team
    return if parent_folder.team_id == team_id

    errors.add(:parent_folder, I18n.t('activerecord.errors.models.project_folder.attributes.parent_folder_team'))
  end

  def parent_folder_validation
    if parent_folder.id == id
      errors.add(:parent_folder, I18n.t('activerecord.errors.models.project_folder.attributes.parent_folder'))
    elsif ProjectFolder.inner_folders(team, self).where(id: parent_folder_id).exists?
      errors.add(:parent_folder, I18n.t('activerecord.errors.models.project_folder.attributes.parent_folder_child'))
    end
  end

  def ensure_uniqueness_name_on_moving
    return unless self.class.where(parent_folder: parent_folder).where('name ILIKE ?', name).any?

    regex = /\((\d+)\)/
    max_number = self.class
                     .where(parent_folder: parent_folder)
                     .where('name ILIKE ?', "#{name} (%)")
                     .order(name: :desc)
                     .pluck(:name)
                     .select { |s| s.match(regex) }
                     .map { |s| s.match(regex)[1].to_i }
                     .max || 0

    self.name = name + " (#{max_number + 1})"
  end
end
